/* -*- c -*-
   llvmir-lang.c - LLVMIR fronted gcc interface
   Copyright (C) 2011 Matthew Wahab <mwb.cde@gmail.com>

   This file is part of GCC-LLVMIR.

   GCC-LLVMIR is free software; you can redistribute it and/or modify it under
   the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 3, or (at your option) any later
   version.

   GCC-LLVMIR is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with GCC-LLVMIR; see the file COPYING3.  If not see
   <http://www.gnu.org/licenses/>.  

   -----------------------------------------------------------------------
   This file is based on go-lang.c to which the following copyright notice
   applies.

   go-lang.c -- Go frontend gcc interface.
   Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.

   This file is part of GCC.

   GCC is free software; you can redistribute it and/or modify it under
   the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 3, or (at your option) any later
   version.

   GCC is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with GCC; see the file COPYING3.  If not see
   <http://www.gnu.org/licenses/>.  */

#include "llvmir-system.h"
#include "llvmir-c.h"

/*
 * Types required by the 
 */

/* Language-dependent contents of a type. Parameter variable_size is
   needed to fix a problem in gcc-4.6, which is fixed in gcc.git. (Not
   used.) */
struct GTY((variable_size)) lang_type
{
  char dummy;
};

/* Language-dependent contents of a decl. (Not used.) */
struct GTY(()) lang_decl
{
  char dummy;
};

/* Language-dependent contents of an identifier.  This must include a
   tree_identifier.  */
struct GTY(()) lang_identifier
{
  struct tree_identifier common;
};

/* Language-dependent contents of a function. (Not used.) */
struct GTY(()) language_function
{
  int dummy;
};

/* The resulting tree type.  */
union GTY((desc ("TREE_CODE (&%h.generic) == IDENTIFIER_NODE"),
	   chain_next ("(union lang_tree_node *) TREE_CHAIN (&%h.generic)")))
lang_tree_node
{
  union tree_node GTY((tag ("0"),
		       desc ("tree_node_structure (&%h)"))) generic;
  struct lang_identifier GTY((tag ("1"))) identifier;
};

/*
 * Language hooks, called by the GCC driver to run the front-end.
 *
 * The function are called in the order:
 * - llvmir_langhook_option_lang_mask
 * - llvmir_langhook_init_options_struct
 * - llvmir_langhook_handle_option
 * - llvmir_langhook_post_options
 * - llvmir_langhook_init
 */

/* Select the integer to use for the size_type_node. */
static void
select_size_type_node (void)
{
  /* For gcc-4.6. Choose the type to use for size_t. */
  if (TYPE_MODE(long_unsigned_type_node) == ptr_mode)
    size_type_node = long_unsigned_type_node;
  else if (TYPE_MODE(long_long_unsigned_type_node) == ptr_mode)
    size_type_node = long_long_unsigned_type_node;
  else
    size_type_node = long_unsigned_type_node;
}

/* Initialize the front-end. */
static bool
llvmir_langhook_init (void)
{
#ifdef LLVMIR_GCC_TRUNK
  /* gcc.git* */
  build_common_tree_nodes (false, false);
  build_common_builtin_nodes ();
#else
  /* gcc.4.6.* */
  build_common_tree_nodes (false);
  build_common_tree_nodes_2 (0);
  build_common_builtin_nodes ();
#endif /* LLVMIR_GCC_TRUNK */


  /* Choose the type to use for size_t. */
  if (!size_type_node)
    select_size_type_node();

  /* Initalise the void_list_node. */
  if (!void_list_node)
    void_list_node = build_tree_list (NULL_TREE, void_type_node);

  /* Initalise the LLVMIR front-end */
  llvmir_initialize();
  
  return true;
}

/* The option mask.  */
static unsigned int
llvmir_langhook_option_lang_mask (void)
{
  return CL_LLVMIR;
}

/* Initialize the options structure.  */
static void
llvmir_langhook_init_options_struct (struct gcc_options *opts)
{
  /* See gcc/common.opt for common options. */

  /* Whether signed overflow is precisely defined.  */
  opts->x_flag_wrapv = 1;

  /* Whether to use strict aliasing.  */
  opts->x_flag_strict_aliasing = 1;

  /* Whether to default to avoiding range issues for complex multiply and
     divide.  */
  opts->x_flag_complex_method = 2;

  /* Whether the builtin math functions should set errno.  */
  opts->x_flag_errno_math = 0;
  opts->frontend_set_flag_errno_math = true;

  /* Whether exceptions are used to handle recovering from panics.  */
  opts->x_flag_exceptions = 1;
  opts->x_flag_non_call_exceptions = 1;

  /* Initalise the LLVMIR option data. */
  llvmir_initialize_options(opts);
}

/* Infrastructure for a VEC of char * pointers.  */

typedef const char *llvmir_char_p;
DEF_VEC_P(llvmir_char_p);
DEF_VEC_ALLOC_P(llvmir_char_p, heap);

/* The list of directories to search after all the llvmir specific
   directories have been searched.  */

static VEC(llvmir_char_p, heap) *llvmir_search_dirs;

/* Handle llvmir specific options. */
static bool
llvmir_langhook_handle_option (
    size_t scode,
    const char *arg ATTRIBUTE_UNUSED,
    int value,
    int kind ATTRIBUTE_UNUSED,
    location_t loc ATTRIBUTE_UNUSED,
    const struct cl_option_handlers *handlers ATTRIBUTE_UNUSED)
{
  enum opt_code code = (enum opt_code) scode;
  bool ret = true;

  switch (code)
    {
      /* Specify a directory to include. */
    case OPT_I:
      ret = llvmir_option_include(arg) ? true : false;
      break;

      /* Choose the LLVMIR language. */
    case OPT_fllvmir_lang_:
      ret = llvmir_option_lang(arg, value) ? true : false;
      break;

      /* Options for the LLVMIR parser */
    case OPT_fllvmir_opt_:
      ret = llvmir_option_opt(arg) ? true : false;
      break;

      /* Select internal debugging output. */
    case OPT_fllvmir_dump_:
      ret = llvmir_option_dump(arg, value) ? true : false;
      break;

      /* Generic debugging option. */
    case OPT_fllvmir_debug_:
      ret = llvmir_option_debug(arg, value) ? true : false;
      break;

    default:
      /* Just indicate that the option is valid.  */
      ret = true;
      break;
    }

  return ret;
}

/* Run after parsing options.  */
static bool
llvmir_langhook_post_options (const char **pfilename ATTRIBUTE_UNUSED)
{
  VEC_free (llvmir_char_p, heap, llvmir_search_dirs);
  llvmir_search_dirs = NULL;

  if (flag_excess_precision_cmdline == EXCESS_PRECISION_DEFAULT)
    flag_excess_precision_cmdline = EXCESS_PRECISION_STANDARD;

  /* Process the options, setting any values still to be decided. */
  llvmir_process_options();

  /* Returning false means that the backend should be used.  */
  return false;
}

/* Parse the files, handing on to the GCC middle-end. */
static void
llvmir_langhook_parse_file (void)
{
  llvmir_parse_input_files (in_fnames, num_in_fnames, flag_syntax_only);
}

/* Generate a type for a given size and signedness. Follow the
   gimplefe code: use the standard types, returning an error_mark_node
   if no suitable code is available. */
static tree
llvmir_langhook_type_for_size (unsigned int bits, int unsignedp)
{
  tree rtype = lang_hooks.types.type_for_size(bits, unsignedp);
  return rtype ? rtype : error_mark_node;
}

/* Generate a type for a machine mode. */
static tree
llvmir_type_for_mode (enum machine_mode mode, int unsignedp)
{
  /* The code to build a type should be part of the LLVMIR translation
     code.  For now, use the code from the gimplefe (gimple-front-end). */

  if (mode == TYPE_MODE (integer_type_node))
    return unsignedp ? unsigned_type_node : integer_type_node;

  if (mode == TYPE_MODE (signed_char_type_node))
    return unsignedp ? unsigned_char_type_node : signed_char_type_node;

  if (mode == TYPE_MODE (short_integer_type_node))
    return unsignedp ? short_unsigned_type_node : short_integer_type_node;

  if (mode == TYPE_MODE (long_integer_type_node))
    return unsignedp ? long_unsigned_type_node : long_integer_type_node;

  if (mode == TYPE_MODE (long_long_integer_type_node))
    return unsignedp ? long_long_unsigned_type_node : long_long_integer_type_node;

  if (mode == QImode)
    return unsignedp ? unsigned_intQI_type_node : intQI_type_node;

  if (mode == HImode)
    return unsignedp ? unsigned_intHI_type_node : intHI_type_node;

  if (mode == SImode)
    return unsignedp ? unsigned_intSI_type_node : intSI_type_node;

  if (mode == DImode)
    return unsignedp ? unsigned_intDI_type_node : intDI_type_node;

#if HOST_BITS_PER_WIDE_INT >= 64
  if (mode == TYPE_MODE (intTI_type_node))
    return unsignedp ? unsigned_intTI_type_node : intTI_type_node;
#endif

  if (mode == TYPE_MODE (float_type_node))
    return float_type_node;

  if (mode == TYPE_MODE (double_type_node))
    return double_type_node;

  if (mode == TYPE_MODE (long_double_type_node))
    return long_double_type_node;

  if (mode == TYPE_MODE (void_type_node))
    return void_type_node;

  if (mode == TYPE_MODE (build_pointer_type (char_type_node)))
    return (unsignedp
	    ? make_unsigned_type (GET_MODE_PRECISION (mode))
	    : make_signed_type (GET_MODE_PRECISION (mode)));

  if (mode == TYPE_MODE (build_pointer_type (integer_type_node)))
    return (unsignedp
	    ? make_unsigned_type (GET_MODE_PRECISION (mode))
	    : make_signed_type (GET_MODE_PRECISION (mode)));

  if (COMPLEX_MODE_P (mode))
    {
      enum machine_mode inner_mode;
      tree inner_type;

      if (mode == TYPE_MODE (complex_float_type_node))
	return complex_float_type_node;
      if (mode == TYPE_MODE (complex_double_type_node))
	return complex_double_type_node;
      if (mode == TYPE_MODE (complex_long_double_type_node))
	return complex_long_double_type_node;

      if (mode == TYPE_MODE (complex_integer_type_node) && !unsignedp)
	return complex_integer_type_node;

      inner_mode = GET_MODE_INNER (mode);
      inner_type = llvmir_type_for_mode (inner_mode, unsignedp);
      if (inner_type != NULL_TREE)
	return build_complex_type (inner_type);
    }
  else if (VECTOR_MODE_P (mode))
    {
      enum machine_mode inner_mode = GET_MODE_INNER (mode);
      tree inner_type = llvmir_type_for_mode (inner_mode, unsignedp);
      if (inner_type != NULL_TREE)
	return build_vector_type_for_mode (inner_type, mode);
    }

  if (mode == TYPE_MODE (dfloat32_type_node))
    return dfloat32_type_node;
  if (mode == TYPE_MODE (dfloat64_type_node))
    return dfloat64_type_node;
  if (mode == TYPE_MODE (dfloat128_type_node))
    return dfloat128_type_node;

  if (ALL_SCALAR_FIXED_POINT_MODE_P (mode))
    {
      if (mode == TYPE_MODE (short_fract_type_node))
	return unsignedp ? sat_short_fract_type_node : short_fract_type_node;
      if (mode == TYPE_MODE (fract_type_node))
	return unsignedp ? sat_fract_type_node : fract_type_node;
      if (mode == TYPE_MODE (long_fract_type_node))
	return unsignedp ? sat_long_fract_type_node : long_fract_type_node;
      if (mode == TYPE_MODE (long_long_fract_type_node))
	return unsignedp ? sat_long_long_fract_type_node
          : long_long_fract_type_node;

      if (mode == TYPE_MODE (unsigned_short_fract_type_node))
	return unsignedp ? sat_unsigned_short_fract_type_node
          : unsigned_short_fract_type_node;
      if (mode == TYPE_MODE (unsigned_fract_type_node))
	return unsignedp ? sat_unsigned_fract_type_node
          : unsigned_fract_type_node;
      if (mode == TYPE_MODE (unsigned_long_fract_type_node))
	return unsignedp ? sat_unsigned_long_fract_type_node
          : unsigned_long_fract_type_node;
      if (mode == TYPE_MODE (unsigned_long_long_fract_type_node))
	return unsignedp ? sat_unsigned_long_long_fract_type_node
          : unsigned_long_long_fract_type_node;

      if (mode == TYPE_MODE (short_accum_type_node))
	return unsignedp ? sat_short_accum_type_node : short_accum_type_node;
      if (mode == TYPE_MODE (accum_type_node))
	return unsignedp ? sat_accum_type_node : accum_type_node;
      if (mode == TYPE_MODE (long_accum_type_node))
	return unsignedp ? sat_long_accum_type_node : long_accum_type_node;
      if (mode == TYPE_MODE (long_long_accum_type_node))
	return unsignedp ? sat_long_long_accum_type_node
          : long_long_accum_type_node;

      if (mode == TYPE_MODE (unsigned_short_accum_type_node))
	return unsignedp ? sat_unsigned_short_accum_type_node
          : unsigned_short_accum_type_node;
      if (mode == TYPE_MODE (unsigned_accum_type_node))
	return unsignedp ? sat_unsigned_accum_type_node
          : unsigned_accum_type_node;
      if (mode == TYPE_MODE (unsigned_long_accum_type_node))
	return unsignedp ? sat_unsigned_long_accum_type_node
          : unsigned_long_accum_type_node;
      if (mode == TYPE_MODE (unsigned_long_long_accum_type_node))
	return unsignedp ? sat_unsigned_long_long_accum_type_node
          : unsigned_long_long_accum_type_node;

      if (mode == QQmode)
	return unsignedp ? sat_qq_type_node : qq_type_node;
      if (mode == HQmode)
	return unsignedp ? sat_hq_type_node : hq_type_node;
      if (mode == SQmode)
	return unsignedp ? sat_sq_type_node : sq_type_node;
      if (mode == DQmode)
	return unsignedp ? sat_dq_type_node : dq_type_node;
      if (mode == TQmode)
	return unsignedp ? sat_tq_type_node : tq_type_node;

      if (mode == UQQmode)
	return unsignedp ? sat_uqq_type_node : uqq_type_node;
      if (mode == UHQmode)
	return unsignedp ? sat_uhq_type_node : uhq_type_node;
      if (mode == USQmode)
	return unsignedp ? sat_usq_type_node : usq_type_node;
      if (mode == UDQmode)
	return unsignedp ? sat_udq_type_node : udq_type_node;
      if (mode == UTQmode)
	return unsignedp ? sat_utq_type_node : utq_type_node;

      if (mode == HAmode)
	return unsignedp ? sat_ha_type_node : ha_type_node;
      if (mode == SAmode)
	return unsignedp ? sat_sa_type_node : sa_type_node;
      if (mode == DAmode)
	return unsignedp ? sat_da_type_node : da_type_node;
      if (mode == TAmode)
	return unsignedp ? sat_ta_type_node : ta_type_node;

      if (mode == UHAmode)
	return unsignedp ? sat_uha_type_node : uha_type_node;
      if (mode == USAmode)
	return unsignedp ? sat_usa_type_node : usa_type_node;
      if (mode == UDAmode)
	return unsignedp ? sat_uda_type_node : uda_type_node;
      if (mode == UTAmode)
	return unsignedp ? sat_uta_type_node : uta_type_node;
    }

  return NULL_TREE;
}

/* The language hook for llvmir_type_for_mode */
static tree
llvmir_langhook_type_for_mode (enum machine_mode mode, int unsignedp)
{
  return llvmir_type_for_mode(mode, unsignedp);
}

static int
llvmir_langhook_global_bindings_p (void)
{
  return current_function_decl == NULL ? 1 : 0;
}

/* Push a declaration into the current binding level. */
static tree
llvmir_langhook_pushdecl (tree decl ATTRIBUTE_UNUSED)
{
  return NULL;
}

/* Get the current list of declarations as trees. */
static tree
llvmir_langhook_getdecls (void)
{
  return NULL;
}

/* Functions called directly by the generic backend.  */
tree
convert (tree type, tree expr)
{
  if (type == error_mark_node
      || expr == error_mark_node
      || TREE_TYPE (expr) == error_mark_node)
    return error_mark_node;

  if (type == TREE_TYPE (expr))
    return expr;

  if (TYPE_MAIN_VARIANT (type) == TYPE_MAIN_VARIANT (TREE_TYPE (expr)))
    return fold_convert (type, expr);

  switch (TREE_CODE (type))
    {
    case VOID_TYPE:
    case BOOLEAN_TYPE:
      return fold_convert (type, expr);
    case INTEGER_TYPE:
      return fold (convert_to_integer (type, expr));
    case POINTER_TYPE:
      return fold (convert_to_pointer (type, expr));
    case REAL_TYPE:
      return fold (convert_to_real (type, expr));
    case COMPLEX_TYPE:
      return fold (convert_to_complex (type, expr));
    default:
      break;
    }

  gcc_unreachable ();
}


/* Preserve LLVMIR trees from the garbage collector.  */
static GTY(()) tree llvmir_gc_root; 

/* GCC front-end hooks. See gcc/langhooks.h and gcc/langhooks-def.h
   for the list of front-end hooks.  */

/* The language name and frontend inititalizer. */
#undef LANG_HOOKS_NAME
#undef LANG_HOOKS_INIT

#define LANG_HOOKS_NAME			"GNU LLVMIR"
#define LANG_HOOKS_INIT			llvmir_langhook_init

/* Frontend option handling. */
#undef LANG_HOOKS_OPTION_LANG_MASK
#undef LANG_HOOKS_INIT_OPTIONS_STRUCT
#undef LANG_HOOKS_HANDLE_OPTION
#undef LANG_HOOKS_POST_OPTIONS

#define LANG_HOOKS_OPTION_LANG_MASK	llvmir_langhook_option_lang_mask
#define LANG_HOOKS_INIT_OPTIONS_STRUCT	llvmir_langhook_init_options_struct
#define LANG_HOOKS_HANDLE_OPTION	llvmir_langhook_handle_option
#define LANG_HOOKS_POST_OPTIONS		llvmir_langhook_post_options

/* The main entry-point for parsing a file. */
#undef LANG_HOOKS_PARSE_FILE
#define LANG_HOOKS_PARSE_FILE		llvmir_langhook_parse_file

/* Required language hooks. */
#undef LANG_HOOKS_TYPE_FOR_MODE
#undef LANG_HOOKS_TYPE_FOR_SIZE
#undef LANG_HOOKS_GLOBAL_BINDINGS_P
#undef LANG_HOOKS_PUSHDECL
#undef LANG_HOOKS_GETDECLS

#define LANG_HOOKS_TYPE_FOR_MODE	llvmir_langhook_type_for_mode
#define LANG_HOOKS_TYPE_FOR_SIZE	llvmir_langhook_type_for_size
#define LANG_HOOKS_GLOBAL_BINDINGS_P	llvmir_langhook_global_bindings_p
#define LANG_HOOKS_PUSHDECL		llvmir_langhook_pushdecl
#define LANG_HOOKS_GETDECLS		llvmir_langhook_getdecls

struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;

/* Include GENGTYPE generated files. */
#include "gt-llvmir-llvmir-lang.h"
#include "gtype-llvmir.h"

/* End of file */
